// This is the main DLL file.

#include "stdafx.h"
#include <new>

#include "ToolBox.h"

#include "RithmicConnector.h"
#include "ConnectionSetup.h"

#define GOOD 0
#define BAD  1

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Globalization;

namespace Tradex {
	namespace Connectivity {
		namespace Rithmic {

			RithmicConnector::RithmicConnector () {
				pRithmicDirectory = System::IO::Path::Combine (AppDomain::CurrentDomain->BaseDirectory, "Rithmic");
				pRithmicLogFile = String::Empty;

				pOrderCodeMap = gcnew Dictionary<String^, int> ();

				_MarketSubscriptions = gcnew Dictionary<MarketKey^, ActivityFilters> ();
				_InstrumentSubscriptions = gcnew Dictionary<InstrumentKey^, ActivityFilters> ();
			};

			RithmicConnector::~RithmicConnector () {
				if (pNative != nullptr) {
					delete pNative;
				}
			};

			void RithmicConnector::SetCallback(IConnectorCallback^ callback) {
				if (callback == nullptr) {
					pConnectorCallback = callback;
					return;
				}
				if (pConnectorCallback != nullptr) {
					throw gcnew InvalidOperationException ("Connector Callback already registered");
				}
				pConnectorCallback = callback;
			}

			void RithmicConnector::Connect (String^ connectionString) {
				OnStatusUpdate (ConnectionStatusCode::Connecting, nullptr);
				pNative = new NativeConnector(this);

				// As we decided to use the connection string syntax to define connection parameters, we use this
				// utility class from the System.Data.Common namespace.
				DbConnectionStringBuilder^ pConnectionStringBuilder = gcnew DbConnectionStringBuilder ();
				pConnectionStringBuilder->ConnectionString = connectionString;

				System::Object^ out;
				bool cf = pConnectionStringBuilder->TryGetValue ("ConfigFolder", out);
				String^ pConfigFolder = (String^) out;

				String^ pUser = (System::String^) pConnectionStringBuilder["UserName"];
				String^ pPass = (System::String^) pConnectionStringBuilder["Password"];
				String^ pBroker = (System::String^) pConnectionStringBuilder["Broker"];

				// We now load the conection information from the proper broker location.
				if (!String::IsNullOrEmpty (pConfigFolder)) {
					pRithmicDirectory = pConfigFolder;
				}
				ConnectionSetup^ pConnectionSetup = ConnectionSetup::Load (pRithmicDirectory, pBroker);
				// This sets up our environment variables. We pass in the values ourselves...
				char ** pEnviroment = pConnectionSetup->CreateEnvironment (
					pRithmicDirectory, pUser
				);

				REngineParams *oParams = nullptr;
				LoginParams *oLoginParams = nullptr;
				try {
					oParams = new REngineParams ();
					oLoginParams = new LoginParams ();
					
					// Parameters for the engine...
					oParams->sAppName = ToolBox::CreateRString ("Tradex.Rithmic.Connector");
					oParams->sAppVersion = ToolBox::CreateRString ("1.0.0.0");
					oParams->sAdminName = ToolBox::CreateRString ("Administrator@nettecture.com");
					oParams->sAdmCnnctPt = ToolBox::CreateRString (pConnectionSetup->Admin);
					oParams->envp = pEnviroment;
					oParams->pAdmCallbacks = pNative;
					oParams->sLogFilePath = ToolBox::CreateRString (pRithmicLogFile);

					// Parameters for the connection;.
					oLoginParams->sUser = ToolBox::CreateRString (pUser);
					oLoginParams->sPassword = ToolBox::CreateRString (pPass);

					oLoginParams->sMdCnnctPt = ToolBox::CreateRString (pConnectionSetup->MarketData);
					oLoginParams->sTsCnnctPt = ToolBox::CreateRString (pConnectionSetup->TradingSystem);
					oLoginParams->sOrCnnctPt = ToolBox::CreateRString (pConnectionSetup->OrderRelease);
					oLoginParams->sIhCnnctPt = ToolBox::CreateRString (pConnectionSetup->IntradayHistory);

					oLoginParams->pCallbacks = pNative;
				
					pNative->Connect (oParams, oLoginParams);
				} catch(std::bad_alloc) {
					if (oParams != nullptr) {
						delete oParams;
						oParams = nullptr;
					}
					if (oLoginParams != nullptr) {
						delete oLoginParams;
						oLoginParams = nullptr;
					}
				} finally {
					if (oParams != nullptr) {
						ToolBox::DestroyRString(&(oParams->sAppName));
						ToolBox::DestroyRString(&(oParams->sAppVersion));
						ToolBox::DestroyRString(&(oParams->sAdminName));
						ToolBox::DestroyRString(&(oParams->sAdmCnnctPt));
						ToolBox::DestroyRString(&(oParams->sLogFilePath));
						delete oParams;
						oParams = nullptr;
					}
					if (oLoginParams != nullptr) {
						ToolBox::DestroyRString(&(oLoginParams->sUser));
						ToolBox::DestroyRString(&(oLoginParams->sPassword));
						ToolBox::DestroyRString(&(oLoginParams->sMdCnnctPt));
						ToolBox::DestroyRString(&(oLoginParams->sTsCnnctPt));
						ToolBox::DestroyRString(&(oLoginParams->sOrCnnctPt));
						ToolBox::DestroyRString(&(oLoginParams->sIhCnnctPt));
						delete oLoginParams;
						oLoginParams = nullptr;
					}
				}

				pConnectionSetup->DeleteEnvironment (pEnviroment);				
			};
			
			void RithmicConnector::Disconnect () {
				if (pNative != nullptr) {
					pNative->Disconnect ();
					delete pNative;
					pNative = nullptr;
				}
			}

			bool RithmicConnector::IsConnected::get () {
				return pNative != nullptr;
			}
			
			void RithmicConnector::GetAccounts () {
				pNative->GetAccounts ();
			}

			void RithmicConnector::GetAccountOrders (AccountKey^ account) {
			}

			void RithmicConnector::ReplayExecutions(AccountKey^ account, DateTime startMoment, DateTime endMoment) {
				if (endMoment.Ticks == 0) {
					endMoment = DateTime::UtcNow;
				}
				if (startMoment.Ticks == 0) {
					// If no start time, we set it a month before end time
					startMoment = endMoment.AddMonths(-1);
				}

				RApi::AccountInfo rithmicAccount;
				ToolBox::FillRAccountInfo (&rithmicAccount, account);

				int startSsboe = 0;
				int endSsboe = 0;
				ToolBox::CreateRTimeStamp (startMoment, &startSsboe, nullptr);
				ToolBox::CreateRTimeStamp (endMoment, &endSsboe, nullptr);

				pNative->ReplayExecutions (&rithmicAccount, startSsboe, endSsboe);

				ToolBox::ClearRAccountInfo (&rithmicAccount);
			}

			void RithmicConnector::ReplayOrders(AccountKey^ account, DateTime startMoment, DateTime endMoment) {
				if (account == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "account");
				}
				if (endMoment.Ticks == 0) {
					endMoment = DateTime::UtcNow;
				}
				if (startMoment.Ticks == 0) {
					// If no start time, we set it a month before end time
					startMoment = endMoment.AddMonths(-1);
				}

				RApi::AccountInfo rithmicAccount;
				ToolBox::FillRAccountInfo (&rithmicAccount, account);

				int startSsboe = 0;
				int endSsboe = 0;
				ToolBox::CreateRTimeStamp (startMoment, &startSsboe, nullptr);
				ToolBox::CreateRTimeStamp (endMoment, &endSsboe, nullptr);

				pNative->ReplayOrders (&rithmicAccount, startSsboe, endSsboe);

				ToolBox::ClearRAccountInfo (&rithmicAccount);
			}

			void RithmicConnector::SetCallback(IMarketConnectorCallback^ callback) {
				if (callback == nullptr) {
					pMarketConnectorCallback = callback;
					return;
				}
				if (pMarketConnectorCallback != nullptr) {
					throw gcnew InvalidOperationException ("MarketConnector Callback already registered");
				}
				pMarketConnectorCallback = callback;
			}

			void RithmicConnector::Subscribe (InstrumentKey^ instrument, Tradex::Market::ActivityFilters filter) {
				if (instrument == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "instrument");
				}
				tsNCharcb _symbol = ToolBox::CreateRString (instrument->Instrument);
				tsNCharcb _exchange = ToolBox::CreateRString (instrument->Market);

				if (filter == ActivityFilters::None) {
					// We delete any subscription we have.
					if (_InstrumentSubscriptions->ContainsKey (instrument)) {
						_InstrumentSubscriptions->Remove (instrument);
						pNative->UnsubscribeInstrument (&_symbol, &_exchange);
					}
					ToolBox::DestroyRString(&_symbol);
					ToolBox::DestroyRString(&_exchange);
					return;
				} else {
					ActivityFilters oldSubscription = ActivityFilters::None;
					_InstrumentSubscriptions->TryGetValue (instrument, oldSubscription);
					if (oldSubscription == filter) {
						// We already are subscribed with exactly the same filter.
						ToolBox::DestroyRString(&_symbol);
						ToolBox::DestroyRString(&_exchange);
						return;
					}
					if (oldSubscription != ActivityFilters::None) {
						// We have a subscription, so we need to get rid of it.
						pNative->UnsubscribeInstrument (&_symbol, &_exchange);
					}
					int flags = 0;
					if ((int)filter & (int) ActivityFilters::Trades) {
						flags = flags | MD_PRINTS | MD_PRINTS_COND;
					}
					if ((int)filter & (int) ActivityFilters::TradeVolume) {
						flags = flags | MD_TRADE_VOLUME;
					}
					if ((int)filter & (int) ActivityFilters::Quotes) {
						flags = flags | MD_QUOTES;
					}
					if ((int)filter & (int) ActivityFilters::BestQuotes) {
						flags = flags | MD_BEST;
					}
					if ((int)filter & (int) ActivityFilters::Mode) {
						flags = flags | MD_MARKET_MODE;
					}
					if ((int)filter & (int) ActivityFilters::Settlements) {
						flags = flags | MD_SETTLEMENT;
					}
					if ((int)filter & (int) ActivityFilters::Indicators) {
						flags = flags | MD_OPEN | MD_CLOSE | MD_OPENING_INDICATOR | MD_CLOSING_INDICATOR;
					}
					if ((int)filter & (int) ActivityFilters::Extremes) {
						flags = flags | MD_HIGH_LOW;
					}
					pNative->SubscribeInstrument(&_exchange, &_symbol, flags);
					_InstrumentSubscriptions [instrument] = filter;
					if (oldSubscription != ActivityFilters::None) {
						// resume
					}
				}

				ToolBox::DestroyRString(&_symbol);
				ToolBox::DestroyRString(&_exchange);
			}

			void RithmicConnector::Subscribe (MarketKey^ market, Tradex::Market::ActivityFilters filter) {
				if (market == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "market");
				}
				tsNCharcb _exchange = ToolBox::CreateRString (market->Market);

				if (filter == ActivityFilters::None) {
					if (_MarketSubscriptions->ContainsKey (market)) {
						_MarketSubscriptions->Remove (market);
						pNative->UnsubscribeExchange (&_exchange);
					}
					return;
				} else {
					ActivityFilters oldSubscription = ActivityFilters::None;
					_MarketSubscriptions->TryGetValue (market, oldSubscription);
					if (oldSubscription != ActivityFilters::None) {
						// suspend
						pNative->UnsubscribeExchange (&_exchange);
					}
					int flags = 0;
					if ((int)filter & (int) ActivityFilters::Trades) {
						flags = flags | MD_PRINTS | MD_PRINTS_COND;
					}
					if ((int)filter & (int) ActivityFilters::TradeVolume) {
						flags = flags | MD_TRADE_VOLUME;
					}
					if ((int)filter & (int) ActivityFilters::Quotes) {
						flags = flags | MD_QUOTES;
					}
					if ((int)filter & (int) ActivityFilters::BestQuotes) {
						flags = flags | MD_BEST;
					}
					if ((int)filter & (int) ActivityFilters::Mode) {
						flags = flags | MD_MARKET_MODE;
					}
					if ((int)filter & (int) ActivityFilters::Settlements) {
						flags = flags | MD_SETTLEMENT;
					}
					if ((int)filter & (int) ActivityFilters::Indicators) {
						flags = flags | MD_OPEN | MD_CLOSE | MD_OPENING_INDICATOR | MD_CLOSING_INDICATOR;
					}
					if ((int)filter & (int) ActivityFilters::Extremes) {
						flags = flags | MD_HIGH_LOW;
					}
					pNative->SubscribeExchange (&_exchange, flags);
					if (oldSubscription != ActivityFilters::None) {
						// resume
					}
				}

				ToolBox::DestroyRString(&_exchange);
			}

			void RithmicConnector::GetReferenceData (InstrumentKey^ instrument) {
				if (instrument == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "instrument");
				}

				tsNCharcb _symbol = ToolBox::CreateRString (instrument->Instrument);
				tsNCharcb _exchange = ToolBox::CreateRString (instrument->Market);
				pNative->GetReferenceData (&_symbol, &_exchange);
				ToolBox::DestroyRString(&_symbol);
				ToolBox::DestroyRString(&_exchange);
			}

			void RithmicConnector::SetCallback(ITradingConnectorCallback^ callback) {
				if (callback == nullptr) {
					pTradingConnectorCallback = callback;
					return;
				}
				if (pTradingConnectorCallback != nullptr) {
					throw gcnew InvalidOperationException ("TradingConnector Callback already registered");
				}
				pTradingConnectorCallback = callback;
			}

			void RithmicConnector::CreateOrder (
				AccountKey^ accountKey,
				OrderKey^ orderKey,
				OrderInfo^ orderInfo
			) {
				if (orderInfo == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "orderInfo");
				}

				RApi::AccountInfo account;
				OrderParams orderParams;

				// Account
				ToolBox::FillRAccountInfo (&account, accountKey);
				orderParams.pAccount = &account;
				switch (orderInfo->Operation) {
					case OrderAction::Buy:
						orderParams.sBuySellType = sBUY_SELL_TYPE_BUY;
						break;
					case OrderAction::Sell:
						orderParams.sBuySellType = sBUY_SELL_TYPE_SELL;
						break;
				}
				// Quantity
				orderParams.iQty = orderInfo->Quantity;

				switch (orderInfo->OrderType) {
					case OrderType::Market:
						orderParams.sOrderType = sORDER_TYPE_MARKET;
						break;
					case OrderType::MarketIfTouched:
						orderParams.sOrderType = sORDER_TYPE_MKT_IF_TOUCHED;
						break;
					case OrderType::Limit:
						orderParams.sOrderType = sORDER_TYPE_LIMIT;
						break;
					case OrderType::LimitIfTouched:
						orderParams.sOrderType = sORDER_TYPE_LMT_IF_TOUCHED;
						break;
					case OrderType::StopLimit:
						orderParams.sOrderType = sORDER_TYPE_STOP_LIMIT;
						break;
					case OrderType::StopMarket:
						orderParams.sOrderType = sORDER_TYPE_STOP_MARKET;
						break;
				}

				// Instrument
				orderParams.sTicker = ToolBox::CreateRString (orderInfo->Instrument->Instrument);
				orderParams.sExchange = ToolBox::CreateRString (orderInfo->Instrument->Market);

				// Limit
				if (!Double::IsNaN(orderInfo->Limit)) {
					orderParams.dPrice = orderInfo->Limit;
				} else {
					orderParams.dPrice = 0;
				}

				// Duration
				switch (orderInfo->Duration) {
					case OrderDuration::EndOfDay:
						orderParams.sDuration = sORDER_DURATION_DAY;
						break;
					case OrderDuration::GoodTillCancel:
						orderParams.sDuration = sORDER_DURATION_GTC;
						break;
					case OrderDuration::InstantOrCanceled:
						orderParams.sDuration = sORDER_DURATION_IOC;
						break;
				}

				// Finally: Determine order type. We deduct that from whether we have a trigger and/or a limit.
				if (Double::IsNaN(orderInfo->Limit)) {
					orderParams.sOrderType = sORDER_TYPE_MARKET;
				} else {
					orderParams.sOrderType = sORDER_TYPE_LIMIT;
				}

				// And last: the Order key goes into the sUsrMsg. We can extract it there later to hook up again.
				orderParams.sUserMsg = ToolBox::CreateRString (Tradex::Trading::OrderKey::ToTagString(orderKey));

				pNative->SendOrder(&orderParams);

				// Account
				ToolBox::ClearRAccountInfo (&account);
				ToolBox::DestroyRString (&orderParams.sUserMsg);
				ToolBox::DestroyRString (&orderParams.sTicker);
				ToolBox::DestroyRString (&orderParams.sExchange);
			}

			void RithmicConnector::ModifyOrder (
				AccountKey^ accountKey,
				OrderKey^ orderKey, OrderId^ orderConnectionKey,
				OrderInfo^ pOrderInfo,
				OrderModification^ orderModification
			) {
				if (orderConnectionKey == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "orderConnectionKey");
				}

				RApi::AccountInfo account;
				ModifyOrderParams  modifyOrderParams ;
				// Account
				ToolBox::FillRAccountInfo (&account, accountKey);

				modifyOrderParams.pAccount = &account;
				modifyOrderParams.iOrderNum = System::Convert::ToInt32 (orderConnectionKey->Value, CultureInfo::InvariantCulture);

				ToolBox::ClearRAccountInfo (&account);
			}

			void RithmicConnector::CancelOrder (
				AccountKey^ accountKey,
				OrderKey^ orderKey,
				OrderId ^orderConnectionKey
			) {
				if (orderConnectionKey == nullptr) {
					throw gcnew ArgumentException ("value can not be null", "orderConnectionKey");
				}
				int orderId = System::Convert::ToInt32 (orderConnectionKey->Value, CultureInfo::InvariantCulture);

				RApi::AccountInfo account;
				// Account
				ToolBox::FillRAccountInfo (&account, accountKey);
				pNative->CancelOrders (&account);

				ToolBox::ClearRAccountInfo (&account);
			}

			void RithmicConnector::CancelOrders ( 
				AccountKey^ pAccountKey
			) {
				RApi::AccountInfo account;
				// Account
				ToolBox::FillRAccountInfo (&account, pAccountKey);
				//pNative->CancelOrder (&account, orderId);

				ToolBox::ClearRAccountInfo (&account);
			}

			void RithmicConnector::OnAccountUpdate (AccountKey^ account, AccountUpdate^ accountUpdate, PositionUpdate^ position) {
				if (pTradingConnectorCallback != nullptr) {
					pTradingConnectorCallback->AccountUpdate (account, accountUpdate, position);
				}
			}

			void RithmicConnector::OnOrderUpdate (AccountKey^ account, OrderKey^ orderKey, OrderId^ connectionKey, OrderUpdate^ orderUpdate, OrderExecution^ execution) {
				if (pTradingConnectorCallback != nullptr) {
					pTradingConnectorCallback->OrderUpdate (account, orderKey, connectionKey, orderUpdate, execution);
				}
			}

			void RithmicConnector::OnOrdersUpdate(AccountKey^ account, array<OrderUpdate^>^ orders) {
				if (pTradingConnectorCallback != nullptr) {
					pTradingConnectorCallback->OrdersUpdate (account, orders);
				}
			}

			void RithmicConnector::OnActivity (InstrumentKey^ instrument, Activity^ Activity) {
				if (pMarketConnectorCallback != nullptr) {
					pMarketConnectorCallback->Activity (instrument, Activity);
				}
			}

			void RithmicConnector::OnReferenceUpdate (InstrumentKey^ instrument, InstrumentInfo^ instrumentInfo, InstrumentPricing^ instrumentPricing) {
				if (pMarketConnectorCallback != nullptr) {
					pMarketConnectorCallback->InstrumentUpdate (instrument, instrumentInfo, instrumentPricing);
				}
			}

			void RithmicConnector::OnLogEntry (
				System::Diagnostics::TraceLevel traceLevel,
				String^ subject,
				String^ message,
				String^ origin,
				String^ reference
			) {
				if (pConnectorCallback != nullptr) {
					pConnectorCallback->LogEntry (traceLevel, subject, message, origin, reference);
				}
			}

			void RithmicConnector::OnStatusUpdate (
				ConnectionStatusCode code,
				String^ message
			) {
				if (pConnectorCallback != nullptr) {
					pConnectorCallback->StatusUpdate (code, message);
				}
			}

			String^ RithmicConnector::ConfigFolder::get () {
				return pRithmicDirectory;
			}

			void RithmicConnector::ConfigFolder::set (String^ folder) {
				pRithmicDirectory = folder;
			}

			String^ RithmicConnector::LogFile::get () {
				return pRithmicLogFile;
			}

			void RithmicConnector::LogFile::set (String^ file) {
				pRithmicLogFile = file;
			}

		}
	}
}